/* Sending custom ICMP */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <net/ethernet.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>

#include "definitions.hpp"

#define MAX_MTU 1500 /* random */

using namespace std;

void* icmp_thread_func(void* x){
 /* ICMP variables */
    int ret = 0;
    int one = 1;
    //int endian = 0;
    //int done_id = 0;
    int sock_eth, sock_icmp;
    struct sockaddr_in dst;
    struct ether_header *eth_hdr;
    struct ip *ip_hdr_in, *ip_hdr_out;
    char buf_in[MAX_MTU], buf_out[MAX_MTU];
    struct icmp *icmp_hdr_in, *icmp_hdr_out;
    int ip_len, icmp_len, icmp_data_len;

	
    if ((sock_eth = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) {
        perror("socket_eth");
        exit(1);
    }


	if ((sock_icmp = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
        perror("socket_icmp");
        exit(1);
    }

    if ((ret = setsockopt(sock_icmp, IPPROTO_IP, IP_HDRINCL, (char *)&one,
                     sizeof(one))) < 0) {
        perror("setsockopt");
        exit(1);
    }


    eth_hdr     = (struct ether_header *)buf_in;
    ip_hdr_in   = (struct ip *)(buf_in + sizeof(struct ether_header));
    icmp_hdr_in = (struct icmp *)((unsigned char *)ip_hdr_in +
                                                   sizeof(struct ip));

    ip_hdr_out   = (struct ip *)buf_out;
    icmp_hdr_out = (struct icmp *)(buf_out + sizeof(struct ip));


	  while(1) { /* ICMP processing loop */

        if ((ret = recv(sock_eth, buf_in, sizeof(buf_in), 0)) < 0) {
            perror("recv");
            exit(1);
        }

        if (ip_hdr_in->ip_p == IPPROTO_ICMP) {
            if (icmp_hdr_in->icmp_type == ICMP_ECHO) {

                /* Prepare outgoing IP header. */
                ip_hdr_out->ip_v          = ip_hdr_in->ip_v;
                ip_hdr_out->ip_hl         = ip_hdr_in->ip_hl;
                ip_hdr_out->ip_tos        = ip_hdr_in->ip_tos;
                ip_hdr_out->ip_tos        = 0;
                ip_hdr_out->ip_len        = ip_hdr_in->ip_len;
                ip_hdr_out->ip_id         = ip_hdr_in->ip_id;
                ip_hdr_out->ip_off        = 0;
                ip_hdr_out->ip_ttl        = 255;
                ip_hdr_out->ip_p          = IPPROTO_ICMP;
                ip_hdr_out->ip_sum        = 0;
                ip_hdr_out->ip_src.s_addr = ip_hdr_in->ip_dst.s_addr;
                ip_hdr_out->ip_dst.s_addr = ip_hdr_in->ip_src.s_addr;

                ip_hdr_out->ip_sum = in_cksum((unsigned short *)buf_out,
                                              ip_hdr_out->ip_hl);

                /* Prepare outgoing ICMP header. */
                icmp_hdr_out->icmp_type  = 0;
                icmp_hdr_out->icmp_code  = 0;
                icmp_hdr_out->icmp_cksum = 0;
                icmp_hdr_out->icmp_id    = icmp_hdr_in->icmp_id;
                icmp_hdr_out->icmp_seq   = icmp_hdr_in->icmp_seq;
                

                ip_len = ntohs(ip_hdr_out->ip_len);
                icmp_len = ip_len - sizeof(struct iphdr);
                icmp_data_len =  icmp_len - sizeof(struct icmphdr);

                printf("ICMP_ECHO request.\n");

                memcpy(icmp_hdr_out->icmp_data, icmp_hdr_in->icmp_data,
                       icmp_data_len);

                icmp_hdr_out->icmp_cksum =
                    in_cksum((unsigned short *)icmp_hdr_out, icmp_len);

                bzero(&dst, sizeof(dst));
                dst.sin_family = AF_INET;
                dst.sin_addr.s_addr = ip_hdr_out->ip_dst.s_addr;

				ret = sendto(sock_icmp, buf_out, ip_len, 0,(struct sockaddr *)&dst, sizeof(dst));
                    if (ret < 0) {
                        perror("sendto");
                    }
			}
		}
	  }

}